package org.gpp.proj1.robot.part.parser;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;

import org.gpp.proj1.robot.action.ActionCache;
import org.gpp.proj1.robot.action.PartAction;
import org.gpp.proj1.robot.action.parser.ActionFileParser;
import org.gpp.proj1.robot.action.parser.MalformedActionScriptException;
import org.gpp.proj1.robot.action.step.ActionStep;
import org.gpp.proj1.robot.modifier.Modifier;
import org.gpp.proj1.robot.modifier.ModifierFactory;
import org.gpp.proj1.robot.part.Part;
import org.gpp.proj1.robot.part.PartType;
import org.gpp.proj1.robot.part.SparePart;
import org.gpp.proj1.robot.part.emissor.Emissor;
import org.gpp.proj1.util.PropertyLoader;

import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.util.LoggingSystem;

/**
 * Classes that passes .part files, creating
 * RobotPart's. 
 * @author Paolo Victor, paolovictor@gmail.com
 */
public class PartFileParser {
		
	private static final String PART_ACTIONS = "part.actions";
	private static final String PART_SPEED = "part.speed";
	private static final String PART_AMMO = "part.ammo";
	private static final String PART_ATTACK = "part.attack";
	private static final String MODELS_TAG = "part.models";
	private static final String EMISSORS_TAG = "part.particleEmissors";
	private static final String MODIFIERS_TAG = "part.modifiers";
	private static final String DEFENSE_TAG = "part.defense";
	private static final String PRICE_TAG = "part.price";
	private static final String ENERGY_TAG = "part.energy";
	private static final String PART_TYPE = "part.type";
	private static final String PART_NAME = "part.name";

	/**
	 * Parses a particle definition file
	 * 
	 * @param filename
	 * @param robot
	 * @return
	 * @throws FileNotFoundException 
	 */
	public static SparePart parseFile( String filename ) throws InvalidPartFileException, FileNotFoundException {
		
		if( ! new File( filename ).exists() ) {
			throw new FileNotFoundException( "Can't find part file: " + filename );
		}
		
		Properties partProperties = PropertyLoader.loadProperties( filename );

		if( partProperties == null ) {
			throw new RuntimeException( "A problem ocurred while opening the part file: " + filename );
		}
		
		try {
			String name = getPartProperty( PART_NAME, partProperties );
			String type = getPartProperty( PART_TYPE, partProperties );
			int price = Integer.parseInt( getPartProperty( PRICE_TAG, partProperties ) );
			
			Map<String, Integer> attributeMap = new HashMap<String, Integer>();
			
			// Putting parameters
			attributeMap.put( Part.ENERGY, Integer.getInteger( getPartProperty( ENERGY_TAG, partProperties ) ) );
			attributeMap.put( Part.PRICE, Integer.getInteger( getPartProperty( PRICE_TAG, partProperties ) ) );
			attributeMap.put( Part.DEFENSE, Integer.getInteger( getPartProperty( DEFENSE_TAG, partProperties ) ) );
			attributeMap.put( Part.ATTACK, Integer.getInteger( getPartProperty( PART_ATTACK, partProperties ) ) );
			attributeMap.put( Part.AMMO, Integer.getInteger( getPartProperty( PART_AMMO, partProperties ) ) );
			attributeMap.put( Part.SPEED, Integer.getInteger( getPartProperty( PART_SPEED, partProperties ) ) );
			
			// Loading modifiers, emissors and models
			List<Modifier> modifiers = loadModifiers( partProperties );
			List<Emissor> emissors = loadEmissors( partProperties );
			Map<String, String> models = loadModels( partProperties );
			Map<String, String> modelTextures = loadModelTextures( partProperties );
			List<String> actions = loadActions( partProperties );
			
			// Creating part
			return new SparePart( name, PartType.getType( type ), price, attributeMap, modifiers, models, modelTextures, emissors, actions );
		} catch( NumberFormatException e ) {
			LoggingSystem.getLogger().warning( "Invalid part definition file: " + filename );
			LoggingSystem.getLogger().throwing( "PartFileParser", "parseFile", e );
			return null;
		}
	}
	
	/**
	 * Gets a property from the part's properties and throws an exception
	 * when the property is not found
	 * 
	 * @param propertyTag
	 * @param partProperties
	 * @param required <code>true</code> if it's a required property. If a required property is
	 * 					missing, an InvalidPartFileException will be thrown
	 * @return
	 * @throws InvalidPartFileException
	 */
	private static String getPartProperty( String propertyTag, Properties partProperties, boolean required ) throws InvalidPartFileException {
		if( required && ! partProperties.containsKey( propertyTag ) ) {
			throw new InvalidPartFileException( propertyTag );
		}
		
		return partProperties.getProperty( propertyTag );
	}

	/**
	 * Gets a required property from the part's properties and throws an exception
	 * when the property is not found
	 * 
	 * @param propertyTag
	 * @param partProperties
	 * @return
	 * @throws InvalidPartFileException
	 */
	private static String getPartProperty( String propertyTag, Properties partProperties ) throws InvalidPartFileException {
		return getPartProperty(propertyTag, partProperties, true);
	}

	/**
	 * Loads the part's actions, loading them if necessary
	 * 
	 * @param properties
	 * @return
	 * @throws InvalidPartFileException 
	 */
	private static List<String> loadActions( Properties properties ) throws InvalidPartFileException {
		
		List<String> actions = new LinkedList<String>();
		
		String actionNames = getPartProperty( PART_ACTIONS, properties );
		if( actionNames != null ) {
			
			StringTokenizer tk = new StringTokenizer( actionNames, " ," );
			while (tk.hasMoreTokens()) {
				
				String actionName = tk.nextToken();
				String actionFile = getPartProperty( PART_ACTIONS + "." + actionName + ".script", properties );
				
				if (actionFile != null) {
					try {
						List<ActionStep> steps = ActionFileParser.parseFile(actionFile);

						ActionCache.getInstance().addAction(new PartAction(actionName, steps));
						actions.add(actionName);
						
					} catch (MalformedActionScriptException e) {
						LoggingSystem.getLogger().warning("Invalid action definition file: " + actionFile);
						LoggingSystem.getLogger().throwing("PartFileParser", "loadActions", e);
					} catch (IOException e) {
						LoggingSystem.getLogger().warning("Invalid action definition file: " + actionFile);
						LoggingSystem.getLogger().throwing("PartFileParser", "loadActions", e);
					}
				}
			}
		}		
		return actions;
	}

	/**
	 * Loads the part's emissors
	 * @param properties
	 * @return A list with the part's particle emissors
	 * @throws InvalidPartFileException 
	 */
	private static List<Emissor> loadEmissors( Properties properties ) throws InvalidPartFileException {
		
		String emissors = getPartProperty( EMISSORS_TAG, properties );
		List<Emissor> emissorList = new LinkedList<Emissor>();
		
		if( emissors != null ) {
			StringTokenizer tk = new StringTokenizer( emissors, ", " );
			
			while( tk.hasMoreTokens() ) {
				Map<String, Node> parentPoints = new HashMap<String, Node>();
				String emissorName = tk.nextToken();				
				
				String parents = getPartProperty( EMISSORS_TAG + "." + emissorName + ".parents", properties );
			
				StringTokenizer parentsTokens = new StringTokenizer( parents, ", " );
			
				while( parentsTokens.hasMoreTokens() ) {
					String parentName = parentsTokens.nextToken();
					Node pointNode = new Node( parentName + "-" + emissorName );
					
					String emissorPoint = getPartProperty( EMISSORS_TAG + "." + emissorName + ".parent." + parentName + ".point", properties );
					String emissorDirection = getPartProperty( EMISSORS_TAG + "." + emissorName + ".parent." + parentName + ".direction", properties );
				
					StringTokenizer st = new StringTokenizer(emissorPoint, ", ");
					Vector3f point = new Vector3f(Float.parseFloat(st.nextToken()), Float.parseFloat(st.nextToken()), Float.parseFloat(st.nextToken()));
					
					pointNode.setLocalTranslation( point );
					
					st = new StringTokenizer(emissorDirection, ", ");
					pointNode.getLocalRotation().fromAngles(Float.parseFloat(st.nextToken()), Float.parseFloat(st.nextToken()), Float.parseFloat(st.nextToken()));
					
					parentPoints.put( parentName, pointNode );
				}
				
				Emissor emissor = new Emissor( emissorName, parentPoints );
				emissorList.add( emissor );
			}			
		}
		
		return emissorList;
	}

	/**
	 * Loads the part's models
	 * @param models The models string
	 * @param properties The part's properties
	 * @return A map on the modelName->modelNode format 
	 * @throws InvalidPartFileException 
	 */
	private static Map<String, String> loadModels( Properties properties ) throws InvalidPartFileException {
		String models =  getPartProperty( MODELS_TAG, properties );
		Map<String, String> modelMap = new HashMap<String, String>();
		
		if( models != null ) {			
			StringTokenizer tk = new StringTokenizer( models, ", " );
			
			while( tk.hasMoreTokens() ) {
				String modelName = tk.nextToken();
				
				String modelFile = getPartProperty( MODELS_TAG + "." + modelName + ".file", properties );
				
				if( modelFile != null ) {
					modelMap.put( modelName, modelFile );
				}
			}			
		}
		
		return modelMap;
	}
	
	/**
	 * Loads the part models' textures
	 * @param models The models string
	 * @param properties The part's properties
	 * @return A map on the modelName->modelNode format 
	 * @throws InvalidPartFileException 
	 */
	private static Map<String, String> loadModelTextures( Properties properties ) throws InvalidPartFileException {
		String models =  getPartProperty( MODELS_TAG, properties );
		Map<String, String> textureMap = new HashMap<String, String>();
		
		if( models != null ) {			
			StringTokenizer tk = new StringTokenizer( models, ", " );
			
			while( tk.hasMoreTokens() ) {
				String modelName = tk.nextToken();
				
				String textureFile = getPartProperty( MODELS_TAG + "." + modelName + ".texture", properties, false );
				
				if( textureFile != null ) {
					textureMap.put( modelName, textureFile );
				}
			}			
		}
		
		return textureMap;
	}

	/**
	 * Loads the part's modifiers
	 * @param modifiers The modifier string
	 * @return A list with the part's modifiers
	 * @throws InvalidPartFileException 
	 */
	private static List<Modifier> loadModifiers( Properties properties ) throws InvalidPartFileException {
		String modifiers = getPartProperty( MODIFIERS_TAG, properties );
		
		List<Modifier> modifierList = new LinkedList<Modifier>();
		
		if( modifiers != null ) {			
			StringTokenizer tk = new StringTokenizer( modifiers, ", " );
			
			while( tk.hasMoreTokens() ) {
				Modifier modifier = ModifierFactory.getInstance().getModifier( tk.nextToken() );
				modifierList.add( modifier );
			}			
		}
		
		return modifierList;
	}
}
